// tiff.cpp
//
//    tiff file delegate

#include "delegates/tiff.h"
#include <stdio.h>


#include "tiffio.h"

//extern "C" {
//#include "TIFFlib.h" // third party
//}

namespace simple{

    //
    // Internal tiff functions
    //
    // Redefine some of the standard tiff error handlers. We don't
    // want any output or the application to stop.

    // Capture errors
    void TIFFError(const char* /*module*/, const char* fmt, va_list argptr)
    {
        char msg[10240];    
        vsprintf (msg, fmt, argptr);

        // For debugging only
        // std::cout << "TIFFError: " << msg << std::endl;

        throw TIFFException (msg);
    }

    //
    // TIFF
    //
    // TIFF storage delegate

    TIFF* TIFF::_sOnly = 0;

    TIFF& TIFF::gOnly()
    {
        if (!_sOnly) _sOnly = new TIFF ();
        return *_sOnly;
    }

    TIFF::TIFF ()    {}
    TIFF::~TIFF () {}

    DelegateInfo TIFF::info (const std::string& filename)
    {
        DelegateInfo info;
        info.isDelegate    = false;
        info.boundary      = Rect ();
        info.bytesPerPixel = 0;
        info.planes        = 0;

        TIFFSetWarningHandler (0);
        TIFFSetErrorHandler   (TIFFError);

        try {
            ::TIFF* tif = TIFFOpen(filename.c_str(), "r");
            if (!tif)
                return info;

            uint32 w, h;
            if (TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &w) == 0)
                return info;
            if (TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &h) == 0)
                return info;

            info.isDelegate = true;
            info.boundary = Rect (0, 0, w, h);
            info.bytesPerPixel = 3;
            info.planes = 3;
        }
        catch (TIFFException&) {
            // Add any special recovery code here (or rethrow)
        }

        return info;
    }



    Image<Pel8> TIFF::readPel8 (const std::string& filename)
    {
        return readRGB (filename);
    }

    Image<RGB>  TIFF::readRGB  (const std::string& filename)
    {
        Image<RGB> rgbImage;

        TIFFSetWarningHandler (0);
        TIFFSetErrorHandler   (TIFFError);

        try {
            ::TIFF* tif = TIFFOpen(filename.c_str(), "r");
            if (!tif)
                return rgbImage;

            uint32 w, h;
            size_t npixels;
            uint32* raster;

            // This reads the entire image into memory
            TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &w);
            TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &h);
            npixels = w * h;
            raster = (uint32*) _TIFFmalloc(npixels * sizeof (uint32));
            if (raster != 0) {
                if (TIFFReadRGBAImage(tif, w, h, raster, 0)) {
                    // Extract the data we want, i.e. the rgb information
                    Rect boundary (0, 0, w, h);
                    rgbImage = Image<RGB> (boundary);
                    uint32* tiffp = raster;
                    uint32 tiffpel;
                    for (unsigned int y=0; y<h; y++) {
                        // Reverse the order since tiff is bottom to top??
                        RGB* rgb = rgbImage.rowAddress (h-1-y);
                        for (unsigned int x=0; x<w; x++, rgb++) {
                            tiffpel = *tiffp++;
                            rgb->red   = static_cast<Pel8>(TIFFGetR(tiffpel));
                            rgb->green = static_cast<Pel8>(TIFFGetG(tiffpel));
                            rgb->blue  = static_cast<Pel8>(TIFFGetB(tiffpel));
                        }
                    }
                }
                _TIFFfree(raster);
            }
            TIFFClose(tif);
        }
        catch (TIFFException&) {
            // Add any special recovery code here (or rethrow)
        }


        return rgbImage;
    }

    bool TIFF::write (const std::string& filename,
                      const RectImageStorage& pixels, 
                      const DelegateParams& /*params*/)
    {
        TIFFSetWarningHandler (0);
        TIFFSetErrorHandler     (TIFFError);

        try {
            ::TIFF* tif = TIFFOpen(filename.c_str(), "w");
            if (!tif)
                return false;

            if (pixels.isNull()) {
                TIFFClose (tif);
                return false;
            }

            //TODO This assumes RGB
            if (pixels.bytesPerPixel() == 3) {
                // RGB
                TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
                TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8);
                TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3);
            }
            else if (pixels.bytesPerPixel() == 1) {
             // 8-bit monochrome
                TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);
                TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8);
                TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1);
            }
            else {
                //TODO
                // Write as RGB
            }


            TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
            TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, pixels.width() );
            TIFFSetField(tif, TIFFTAG_IMAGELENGTH, pixels.height() );
            TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
            TIFFSetField(tif, TIFFTAG_XRESOLUTION, 1);
            TIFFSetField(tif, TIFFTAG_YRESOLUTION, 1);
            TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, 1);

            TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_LZW);

            // Write one line at a time
            int status;
            for (unsigned int y=0; y<pixels.height(); y++) {
                const void* row = reinterpret_cast<const void*>(
                    pixels._rowAddress (pixels.y0() + y));
                status = TIFFWriteScanline(tif, const_cast<void*>(row), y);
                if (status == -1) {
                    // An error occured
                    TIFFClose (tif);
                    return false;
                }
            }

            TIFFClose (tif);
        }
        catch (TIFFException&) {
            // Add any special recovery code here (or rethrow)
        }

        return true;
    }


    // Add this delegate to our list during static initialization
    TIFF_installer TIFF_installer::installer;
    TIFF_installer::TIFF_installer ()
    {
        ImageIODelegateList::gOnly().setDelegate ("tiff", &TIFF::gOnly());
        ImageIODelegateList::gOnly().setDelegate ("tif", &TIFF::gOnly());
    }
}